package service

import (
	sqlc "bgs/db/sqlc"
	bilinprod "bgs/grpc/gen/proxy2/bilinprod"
	"bgs/grpc/imp/constants"
	"bgs/grpc/imp/enums"
	"bgs/service/model"
	"bgs/util"
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	pg "github.com/lib/pq"
	"strconv"
	"time"
)

type CreateProductParam struct {
	SyncAction     sqlc.EmSyncAction
	BrandID        int64
	Name           string
	Price          int32
	Pics           []string
	Intro          string
	IntroPics      []string
	ProductGroupID int64
	RemotePkOfProd int64
	AttributeDefs  []*model.ProductAttributeDef
	Specs          []*model.ProductSpec
}

type ProductAttributeKeyMap map[string]int64

// CreateProduct 创建商品
func (s *ProductServiceImp) CreateProduct(ctx context.Context, tx *sql.Tx, ptr *CreateProductParam) (
	product sqlc.Product,
	productAttributeKeys []sqlc.ProductAttributeKey,
	productAttributeValues []sqlc.ProductAttributeValue,
	productSpecs []sqlc.ProductSpec,
	err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	q := sqlc.New(tx)
	bilinProdTypeID, err := s.ensureBilinProdType(ctx, ptr.BrandID)
	if err != nil {
		return
	}
	slog.Infof("1 确保比邻商品分类 ok")

	syncAction := ptr.SyncAction
	var origin sqlc.EmDataSource
	var bilinProd model.BilinProd
	var bilinSkuPtrMap model.BilinProdSkuPtrMap
	remotePkOfProd := ptr.RemotePkOfProd
	if remotePkOfProd == 0 {
		origin = sqlc.EmDataSourceLocal
		// syncAction = sqlc.EmSyncActionLCR // 本地发起创建并同步到比邻
		bilinProd, bilinSkuPtrMap, err = s.createBilinProd(ctx, ptr, bilinProdTypeID)
		if err != nil {
			return
		}
		remotePkOfProd = bilinProd.RemotePkOfProd
		slog.Infof("1 创建比邻商品 ok", bilinProd)
	} else {
		origin = sqlc.EmDataSourceRemote
		// syncAction = sqlc.EmSyncActionRCL // 远程创建并在本地开通星团
		bilinProd, bilinSkuPtrMap, err = s.getBilinProd(ctx, ptr.BrandID, remotePkOfProd)
		if err != nil {
			return
		}
		slog.Info("1 无需创建比邻商品 ok")
	}
	txTime := time.Now()
	var priceMin int32
	var priceMax int32
	hasSpecs := len(ptr.Specs) > 0

	if hasSpecs {
		maxProductSpec, minProductSpec := util.MaxWithMin(util.ToGenericSlice(ptr.Specs), func(i1, i2 interface{}) util.Ord {
			ps1, ps2 := i1.(*model.ProductSpec), i2.(*model.ProductSpec)
			return (util.IfThenElse(ps1.Price-ps2.Price < 0, util.Lt, util.Gt)).(util.Ord)
		})
		priceMax, priceMin = maxProductSpec.(*model.ProductSpec).Price, minProductSpec.(*model.ProductSpec).Price
	} else {
		priceMax = ptr.Price
		priceMin = ptr.Price
	}
	slog.Infof("max,min %v, %v", priceMax, priceMin)

	product, err = q.CreateProduct(ctx, sqlc.CreateProductParams{
		Origin:          string(origin),
		BrandID:         ptr.BrandID,
		Name:            ptr.Name,
		Pics:            ptr.Pics,
		Intro:           sqlc.NSFromString(ptr.Intro),
		IntroPics:       ptr.IntroPics,
		HasSpecs:        hasSpecs,
		PriceMin:        priceMin,
		PriceMax:        priceMax,
		ProductGroupID:  sqlc.NInt64FromInt64(ptr.ProductGroupID),
		RemotePrdTypeID: int64(bilinProdTypeID),
		RemotePk:        remotePkOfProd,
	})
	if err != nil {
		return
	}
	slog.Infof("a1 创建产品 ok")
	localPKOfProduct := strconv.FormatInt(product.ID, 10)
	remotePkOfProduct := strconv.FormatInt(product.RemotePk, 10)

	//2 商品设置同步
	localObjOfProduct, localPkNameOfProduct, localFieldsOfProduct := constants.SyncLocalObjProduct, constants.SyncLocalPkNameProduct, constants.SyncLocalFieldsProduct
	remoteObjOfProduct, remotePkNameOfProduct, remoteFieldsOfProduct := constants.SyncRemoteObjProduct, constants.SyncRemotePkNameProduct, constants.SyncRemoteFieldsProduct

	syncOfProduct, err := q.UpsertSync(ctx, sqlc.UpsertSyncParams{
		LocalObj:         localObjOfProduct,
		LocalPkName:      localPkNameOfProduct,
		LocalFields:      localFieldsOfProduct,
		RemoteObj:        remoteObjOfProduct,
		RemotePkName:     remotePkNameOfProduct,
		RemoteFields:     remoteFieldsOfProduct,
		LatestSynced:     txTime,
		LatestSyncAction: string(syncAction),
	})
	if err != nil {
		return
	}
	slog.Info("a2 商品同步 ok")

	// 3 设置商品同步映射
	syncMappingOfProduct, err := q.UpsertSyncMapping(ctx, sqlc.UpsertSyncMappingParams{
		SyncID:    syncOfProduct.ID,
		LocalObj:  localObjOfProduct,
		LocalPk:   localPKOfProduct,
		RemoteObj: remoteObjOfProduct,
		RemotePk:  remotePkOfProduct,
	})
	if err != nil {
		return
	}
	slog.Info("a3 商品同步映射 ok")

	// 4 设置同步日志
	syncDataOfProduct, err := json.Marshal(product)
	if err != nil {
		return
	}

	maxVer, err := q.GetSyncLogMaxVer(ctx, sqlc.NInt64FromInt64(syncMappingOfProduct.ID))
	if err != nil {
		return
	}
	_, err = q.CreateSyncLog(ctx, sqlc.CreateSyncLogParams{
		SyncID:        syncOfProduct.ID,
		SyncMappingID: sqlc.NInt64FromInt64(syncMappingOfProduct.ID),
		LocalObj:      localObjOfProduct,
		LocalPk:       localPKOfProduct,
		RemoteObj:     remoteObjOfProduct,
		RemotePk:      remotePkOfProduct,
		Synced:        txTime,
		Action:        string(syncAction),
		Data:          syncDataOfProduct,
		SyncStatus:    string(enums.SyncStatusDone),
		Ver:           int64(maxVer + 1),
	})
	if err != nil {
		return
	}
	slog.Info("a4 商品同步日志 ok")

	// 5 设置同步远程对象
	_, err = q.UpsertSyncRemote(ctx, sqlc.UpsertSyncRemoteParams{
		SyncID:    syncOfProduct.ID,
		RemoteObj: remoteObjOfProduct,
		RemotePk:  remotePkOfProduct,
		Data:      bilinProd.Raw,
	})

	if err != nil {
		return
	}
	slog.Info("a5 商品同步远程对象 ok")

	if hasSpecs {
		// 目前有规格记录是可能没有规格属性,(上游系统无规格还是有一条规格记录)
		if len(ptr.AttributeDefs) > 0 {

			// 6 批量插入产品属性键
			batchInsertProductAttributeKeySQL := pg.CopyIn("product_attribute_keys", "product_id", "name", "sort_no")
			stmtOfProductAttributeKey, sqlErr1 := tx.PrepareContext(ctx, batchInsertProductAttributeKeySQL)
			if sqlErr1 != nil {
				err = sqlErr1
				return
			}
			for idx, item := range ptr.AttributeDefs {
				_, err = stmtOfProductAttributeKey.ExecContext(ctx, product.ID, item.Key, idx+1)
				if err != nil {
					return
				}
			}
			err = stmtOfProductAttributeKey.Close()
			if err != nil {
				return
			}
			slog.Infof("a6 批量插入产品属性键 ok")

			productAttributeKeys, err = q.ListNormalProductAttributeKeysByProductID(ctx, product.ID)
			if err != nil {
				return
			}
			slog.Infof("a7 读取插入的产品属性键(%d条) ok", len(productAttributeKeys))

			productAttributeKeyMap := make(ProductAttributeKeyMap)
			for _, s := range productAttributeKeys {
				productAttributeKeyMap[s.Name] = s.ID
			}

			batchInsertProductAttributeValueSQL := pg.CopyIn("product_attribute_values", "product_id", "product_attribute_key_id", "value", "sort_no")
			stmtOfProductAttributeValue, sqlErr2 := tx.PrepareContext(ctx, batchInsertProductAttributeValueSQL)
			if sqlErr2 != nil {
				err = sqlErr2
				return
			}
			x := 0
			for _, itemKey := range ptr.AttributeDefs {
				for idx, itemValue := range itemKey.Values {
					_, err = stmtOfProductAttributeValue.ExecContext(ctx, product.ID, productAttributeKeyMap[itemKey.Key], itemValue, idx+1)
					if err != nil {
						return
					}
					x++
				}
			}
			err = stmtOfProductAttributeValue.Close()
			if err != nil {
				return
			}
			slog.Infof("a8 批量插入产品属性值(%d条) ok", x)

			productAttributeValues, err = q.ListNormalProductAttributeValuesByProductID(ctx, product.ID)
			if err != nil {
				return
			}
			slog.Infof("a9 读取插入的产品属性值(%d条) ok", len(productAttributeValues))
		}

		var produceSpec sqlc.ProductSpec
		var syncMappingOfProductSpec sqlc.SyncMapping
		//12 商品设置同步
		localObjOfProductSpec, localPkNameOfProductSpec, localFieldsOfProductSpec := constants.SyncLocalObjProductSpec, constants.SyncLocalPkNameProductSpec, constants.SyncLocalFieldsProductSpec
		remoteObjOfProductSpec, remotePkNameOfProductSpec, remoteFieldsOfProductSpec := constants.SyncRemoteObjProductSpec, constants.SyncRemotePkNameProductSpec, constants.SyncRemoteFieldsProductSpec
		syncOfProductSpec, sqlErr5 := q.UpsertSync(ctx, sqlc.UpsertSyncParams{
			LocalObj:         localObjOfProductSpec,
			LocalPkName:      localPkNameOfProductSpec,
			LocalFields:      localFieldsOfProductSpec,
			RemoteObj:        remoteObjOfProductSpec,
			RemotePkName:     remotePkNameOfProductSpec,
			RemoteFields:     remoteFieldsOfProductSpec,
			LatestSynced:     txTime,
			LatestSyncAction: string(syncAction),
		})
		if sqlErr5 != nil {
			err = sqlErr5
			return
		}
		slog.Info("a10 商品规格同步 ok")

		for _, item := range ptr.Specs {
			// 方法3,因为在一个事务里,数量也不大,可以用sqlc的单个插入
			k := util.JoinString(util.MapToString(util.ToGenericSlice(item.Attributes), func(v interface{}) string {
				val := v.(model.ProductAttribute)
				return val.Value
			}), "/")
			bilinProdSkuPtr := bilinSkuPtrMap[k]

			bytesOfAttributes, marshalErr := json.Marshal(item.Attributes)
			if marshalErr != nil {
				err = marshalErr
				return
			}

			produceSpec, err = q.UpsertProductSpec(ctx, sqlc.UpsertProductSpecParams{
				Origin:            product.Origin,
				BrandID:           product.BrandID,
				ProductID:         product.ID,
				ProductAttributes: json.RawMessage(bytesOfAttributes),
				Name:              item.Name,
				Price:             item.Price,
				RemotePk:          bilinProdSkuPtr.RemotePkOfSku,
			})
			if err != nil {
				return
			}
			productSpecs = append(productSpecs, produceSpec)

			localPKOfProductSpec := strconv.FormatInt(produceSpec.ID, 10)
			remotePkOfProductSpec := strconv.FormatInt(produceSpec.RemotePk, 10)
			// 设置商品规格同步映射
			syncMappingOfProductSpec, err = q.UpsertSyncMapping(ctx, sqlc.UpsertSyncMappingParams{
				SyncID:    syncOfProductSpec.ID,
				LocalObj:  localObjOfProductSpec,
				LocalPk:   localPKOfProductSpec,
				RemoteObj: remoteObjOfProductSpec,
				RemotePk:  remotePkOfProductSpec,
			})
			if err != nil {
				return
			}

			// 10 设置商品规格同步日志
			syncDataOfProductSpec, errMarshal := json.Marshal(produceSpec)
			if errMarshal != nil {
				err = errMarshal
				return
			}
			maxVer, sqlErr3 := q.GetSyncLogMaxVer(ctx, sqlc.NInt64FromInt64(syncMappingOfProductSpec.ID))
			if sqlErr3 != nil {
				err = sqlErr3
				return
			}
			_, err = q.CreateSyncLog(ctx, sqlc.CreateSyncLogParams{
				SyncID:        syncOfProductSpec.ID,
				SyncMappingID: sqlc.NInt64FromInt64(syncMappingOfProductSpec.ID),
				LocalObj:      localObjOfProductSpec,
				LocalPk:       localPKOfProductSpec,
				RemoteObj:     remoteObjOfProductSpec,
				RemotePk:      remotePkOfProductSpec,
				Synced:        txTime,
				Action:        string(syncAction),
				Data:          syncDataOfProductSpec,
				SyncStatus:    string(enums.SyncStatusDone),
				Ver:           int64(maxVer + 1),
			})
			if err != nil {
				return
			}

			// 设置grpc商品属性键同步远程对象
			_, err = q.UpsertSyncRemote(ctx, sqlc.UpsertSyncRemoteParams{
				SyncID:    syncOfProductSpec.ID,
				RemoteObj: remoteObjOfProductSpec,
				RemotePk:  remotePkOfProductSpec,
				Data:      bilinProdSkuPtr.Raw,
			})

			if err != nil {
				return
			}
		}
		slog.Infof("13 批量插入商品规格、grpc商品规格同步映射、grpc商品规格同步日志、grpc商品规格同步远程对象 ok")
	} else {
		slog.Infof("无规格商品")
	}

	slog.Infof("b 创建商品 ok")
	return
}

type UpdateProductParam struct {
	SyncAction     sqlc.EmSyncAction
	ProductID      int64
	BrandID        int64
	Name           string
	Price          int32
	Pics           []string
	Intro          string
	IntroPics      []string
	ProductGroupID int64
	AttributeDefs  []*model.ProductAttributeDef
	Specs          []*model.ProductSpec
}

// UpdateProduct 更新商品
func (s *ProductServiceImp) UpdateProduct(ctx context.Context, tx *sql.Tx, ptr *UpdateProductParam) (
	product sqlc.Product,
	productAttributeKeys []sqlc.ProductAttributeKey,
	productAttributeValues []sqlc.ProductAttributeValue,
	productSpecs []sqlc.ProductSpec,
	err error) {

	slog := util.NewContextLogger(ctx, s.slog)

	q := sqlc.New(tx)
	bilinProdTypeID, err := s.ensureBilinProdType(ctx, ptr.BrandID)
	if err != nil {
		return
	}
	slog.Infof("1 确保比邻商品分类 ok")

	productID := ptr.ProductID
	txTime := time.Now()
	var priceMin int32
	var priceMax int32
	hasSpecs := len(ptr.Specs) > 0
	syncAction := ptr.SyncAction

	// 更新前置分析
	noramlProductAttributeKeysExist, err := q.ListNormalProductAttributeKeysByProductID(ctx, productID)
	if err != nil {
		return
	}

	var newKeyValues []string
	// 找出需要disabled的keys
	newKeyNames := util.MapToString(util.ToGenericSlice(ptr.AttributeDefs), func(v interface{}) string {
		def := v.(*model.ProductAttributeDef)
		newKeyValues = append(newKeyValues, def.Values...)
		return def.Key
	})
	toDisableKeyIDs := util.MapToInt64(util.Filter(util.ToGenericSlice(noramlProductAttributeKeysExist), func(v interface{}) bool {
		key := v.(sqlc.ProductAttributeKey)
		return !util.StringContains(newKeyNames, key.Name)
	}), func(v interface{}) int64 {
		key := v.(sqlc.ProductAttributeKey)
		return key.ID
	})
	slog.Infof("toDisableKeyIDs->%v", toDisableKeyIDs)

	normalProductAttributeValuesExist, err := q.ListNormalProductAttributeValuesByProductID(ctx, productID)
	if err != nil {
		return
	}
	// 找出需要disabled的values
	toDisableValueIDs1 := util.MapToInt64(util.Filter(util.ToGenericSlice(normalProductAttributeValuesExist), func(v interface{}) bool {
		val := v.(sqlc.ProductAttributeValue)
		return !util.StringContains(newKeyValues, val.Value)
	}), func(v interface{}) int64 {
		val := v.(sqlc.ProductAttributeValue)
		return val.ID
	})
	slog.Infof("toDisableValueIDs1 from newValues->%v", toDisableValueIDs1)

	productAttributeValuesOfToDisableKeys, err := q.ListNormalProductAttributeValuesByProductAttributeKeyIDs(ctx, toDisableKeyIDs)
	if err != nil {
		return
	}
	toDisableValueIDs2 := util.MapToInt64(util.ToGenericSlice(productAttributeValuesOfToDisableKeys), func(v interface{}) int64 {
		val := v.(sqlc.ProductAttributeValue)
		return val.ID
	})
	slog.Infof("toDisableValueIDs2->%v", toDisableValueIDs2)

	toDisableValueIDs := append(toDisableValueIDs1, toDisableValueIDs2...)
	slog.Infof("toDisableValueIDs->%v", toDisableValueIDs)

	newSpecNames := util.MapToString(util.ToGenericSlice(ptr.Specs), func(v interface{}) string {
		val := v.(*model.ProductSpec)
		return val.Name
	})

	productSpecsExist, err := q.ListNormalProductSpecsByProductID(ctx, productID)
	if err != nil {
		return
	}
	toDisableSpecIDs := util.MapToInt64(util.Filter(util.ToGenericSlice(productSpecsExist), func(v interface{}) bool {
		spec := v.(sqlc.ProductSpec)
		return !util.StringContains(newSpecNames, spec.Name)
	}), func(v interface{}) int64 {
		val := v.(sqlc.ProductSpec)
		return val.ID
	})
	slog.Infof("toDisableSpecIDs->%v", toDisableSpecIDs)

	if hasSpecs {
		maxProductSpec, minProductSpec := util.MaxWithMin(util.ToGenericSlice(ptr.Specs), func(i1, i2 interface{}) util.Ord {
			ps1, ps2 := i1.(*model.ProductSpec), i2.(*model.ProductSpec)
			return (util.IfThenElse(ps1.Price-ps2.Price < 0, util.Lt, util.Gt)).(util.Ord)
		})
		priceMax, priceMin = maxProductSpec.(*model.ProductSpec).Price, minProductSpec.(*model.ProductSpec).Price
	} else {
		priceMax = ptr.Price
		priceMin = ptr.Price
	}
	slog.Infof("max,min %v, %v", priceMax, priceMin)

	slog.Info("更新前置分析完毕")

	product, err = q.UpdateProduct(ctx, sqlc.UpdateProductParams{
		ID:              productID,
		Name:            ptr.Name,
		Pics:            ptr.Pics,
		Intro:           sqlc.NSFromString(ptr.Intro),
		IntroPics:       ptr.IntroPics,
		HasSpecs:        hasSpecs,
		PriceMin:        priceMin,
		PriceMax:        priceMax,
		ProductGroupID:  sqlc.NInt64FromInt64(ptr.ProductGroupID),
		RemotePrdTypeID: int64(bilinProdTypeID),
	})
	if err != nil {
		return
	}
	slog.Info("a1 更新商品 ok")
	localPKOfProduct := strconv.FormatInt(product.ID, 10)
	remotePkOfProduct := strconv.FormatInt(product.RemotePk, 10)

	bilinProd, bilinSkuPtrMap, err := s.editBilinProd(ctx, ptr, uint64(product.RemotePk), bilinProdTypeID)
	if err != nil {
		return
	}
	slog.Info("a2 获取比邻商品 ok")

	//2 商品设置同步
	localObjOfProduct, localPkNameOfProduct, localFieldsOfProduct := constants.SyncLocalObjProduct, constants.SyncLocalPkNameProduct, constants.SyncLocalFieldsProduct
	remoteObjOfProduct, remotePkNameOfProduct, remoteFieldsOfProduct := constants.SyncRemoteObjProduct, constants.SyncRemotePkNameProduct, constants.SyncRemoteFieldsProduct

	syncOfProduct, err := q.UpsertSync(ctx, sqlc.UpsertSyncParams{
		LocalObj:         localObjOfProduct,
		LocalPkName:      localPkNameOfProduct,
		LocalFields:      localFieldsOfProduct,
		RemoteObj:        remoteObjOfProduct,
		RemotePkName:     remotePkNameOfProduct,
		RemoteFields:     remoteFieldsOfProduct,
		LatestSynced:     txTime,
		LatestSyncAction: string(syncAction),
	})
	if err != nil {
		return
	}
	slog.Info("a3 商品同步 ok")

	// 3 设置同步映射
	syncMappingOfProduct, err := q.UpsertSyncMapping(ctx, sqlc.UpsertSyncMappingParams{
		SyncID:    syncOfProduct.ID,
		LocalObj:  localObjOfProduct,
		LocalPk:   localPKOfProduct,
		RemoteObj: remoteObjOfProduct,
		RemotePk:  remotePkOfProduct,
	})
	if err != nil {
		return
	}
	slog.Info("a4 商品同步映射 ok")

	// 4 设置同步日志
	syncDataOfProduct, err := json.Marshal(product)
	if err != nil {
		return
	}

	maxVer, err := q.GetSyncLogMaxVer(ctx, sqlc.NInt64FromInt64(syncMappingOfProduct.ID))
	if err != nil {
		return
	}
	_, err = q.CreateSyncLog(ctx, sqlc.CreateSyncLogParams{
		SyncID:        syncOfProduct.ID,
		SyncMappingID: sqlc.NInt64FromInt64(syncMappingOfProduct.ID),
		LocalObj:      localObjOfProduct,
		LocalPk:       localPKOfProduct,
		RemoteObj:     remoteObjOfProduct,
		RemotePk:      remotePkOfProduct,
		Synced:        txTime,
		Action:        string(syncAction),
		Data:          syncDataOfProduct,
		SyncStatus:    string(enums.SyncStatusDone),
		Ver:           int64(maxVer + 1),
	})
	if err != nil {
		return
	}
	slog.Info("a5 商品同步日志 ok")

	// 5 设置同步远程对象
	_, err = q.UpsertSyncRemote(ctx, sqlc.UpsertSyncRemoteParams{
		SyncID:    syncOfProduct.ID,
		RemoteObj: remoteObjOfProduct,
		RemotePk:  remotePkOfProduct,
		Data:      bilinProd.Raw,
	})

	if err != nil {
		return
	}
	slog.Info("a6 商品同步远程对象 ok")

	if len(toDisableKeyIDs) > 0 {
		_, err = q.DisableProductAttributeKeyByIDs(ctx, toDisableKeyIDs)
		if err != nil {
			return
		}
		slog.Infof("a7 disable商品属性键 ok")
	} else {
		slog.Info("a7 没有要disable的商品属性键 ok")
	}

	if len(toDisableValueIDs) > 0 {
		_, err = q.DisableProductAttributeValueByIDs(ctx, toDisableValueIDs)
		if err != nil {
			return
		}
		slog.Infof("a8 disable商品属性值 ok")
	} else {
		slog.Info("a8 没有要disable的商品属性值 ok")
	}

	if len(toDisableSpecIDs) > 0 {
		_, err = q.DisableProductSpecByIDs(ctx, toDisableSpecIDs)
		if err != nil {
			return
		}
		slog.Infof("a9 disable商品规格 ok")
	} else {
		slog.Info("a9 没有要disable的商品规格 ok")
	}

	for idx1, item1 := range ptr.AttributeDefs {
		r1, sqlErr := q.UpsertProductAttributeKey(ctx, sqlc.UpsertProductAttributeKeyParams{
			ProductID: product.ID,
			Name:      item1.Key,
			SortNo:    int32(idx1 + 1),
			// RemotePk:  item1.RemotePk,
		})
		if sqlErr != nil {
			err = sqlErr
			return
		}
		productAttributeKeys = append(productAttributeKeys, r1)
		for idx2, item2 := range item1.Values {
			r2, sqlErr2 := q.UpsertProductAttributeValue(ctx, sqlc.UpsertProductAttributeValueParams{
				ProductID:             product.ID,
				ProductAttributeKeyID: r1.ID,
				Value:                 item2,
				SortNo:                int32(idx2 + 1),
			})
			if sqlErr2 != nil {
				err = sqlErr2
				return
			}
			productAttributeValues = append(productAttributeValues, r2)
		}
		if err != nil {
			return
		}
	}
	slog.Infof("a10 批量upsert产品属性键/值 ok")

	var produceSpec sqlc.ProductSpec
	var syncMappingOfProductSpec sqlc.SyncMapping
	//11 商品设置同步
	localObjOfProductSpec, localPkNameOfProductSpec, localFieldsOfProductSpec := constants.SyncLocalObjProductSpec, constants.SyncLocalPkNameProductSpec, constants.SyncLocalFieldsProductSpec
	remoteObjOfProductSpec, remotePkNameOfProductSpec, remoteFieldsOfProductSpec := constants.SyncRemoteObjProductSpec, constants.SyncRemotePkNameProductSpec, constants.SyncRemoteFieldsProductSpec
	syncOfProductSpec, err := q.UpsertSync(ctx, sqlc.UpsertSyncParams{
		LocalObj:         localObjOfProductSpec,
		LocalPkName:      localPkNameOfProductSpec,
		LocalFields:      localFieldsOfProductSpec,
		RemoteObj:        remoteObjOfProductSpec,
		RemotePkName:     remotePkNameOfProductSpec,
		RemoteFields:     remoteFieldsOfProductSpec,
		LatestSynced:     txTime,
		LatestSyncAction: string(syncAction),
	})
	if err != nil {
		return
	}
	slog.Info("a11 商品规格同步 ok")

	for _, item := range ptr.Specs {
		k := util.JoinString(util.MapToString(util.ToGenericSlice(item.Attributes), func(v interface{}) string {
			val := v.(model.ProductAttribute)
			return val.Value
		}), "/")
		bilinProdSkuPtr := bilinSkuPtrMap[k]

		bytesOfAttributes, marshalErr := json.Marshal(item.Attributes)
		if marshalErr != nil {
			err = marshalErr
			return
		}

		produceSpec, err = q.UpsertProductSpec(ctx, sqlc.UpsertProductSpecParams{
			Origin:            product.Origin,
			BrandID:           product.BrandID,
			ProductID:         product.ID,
			ProductAttributes: json.RawMessage(bytesOfAttributes),
			Name:              item.Name,
			Price:             item.Price,
			RemotePk:          bilinProdSkuPtr.RemotePkOfSku,
		})
		if err != nil {
			return
		}

		productSpecs = append(productSpecs, produceSpec)

		localPKOfProductSpec := strconv.FormatInt(produceSpec.ID, 10)
		remotePkOfProductSpec := strconv.FormatInt(produceSpec.RemotePk, 10)
		// 设置商品规格同步映射
		syncMappingOfProductSpec, err = q.UpsertSyncMapping(ctx, sqlc.UpsertSyncMappingParams{
			SyncID:    syncOfProductSpec.ID,
			LocalObj:  localObjOfProductSpec,
			LocalPk:   localPKOfProductSpec,
			RemoteObj: remoteObjOfProductSpec,
			RemotePk:  remotePkOfProductSpec,
		})
		if err != nil {
			return
		}

		// 10 设置商品规格同步日志
		syncDataOfProductSpec, marshalErr := json.Marshal(produceSpec)
		if marshalErr != nil {
			err = marshalErr
			return
		}
		maxVer, sqlErr := q.GetSyncLogMaxVer(ctx, sqlc.NInt64FromInt64(syncMappingOfProductSpec.ID))
		if sqlErr != nil {
			err = sqlErr
			return
		}
		_, err = q.CreateSyncLog(ctx, sqlc.CreateSyncLogParams{
			SyncID:        syncOfProductSpec.ID,
			SyncMappingID: sqlc.NInt64FromInt64(syncMappingOfProductSpec.ID),
			LocalObj:      localObjOfProductSpec,
			LocalPk:       localPKOfProductSpec,
			RemoteObj:     remoteObjOfProductSpec,
			RemotePk:      remotePkOfProductSpec,
			Synced:        txTime,
			Action:        string(syncAction),
			Data:          syncDataOfProductSpec,
			SyncStatus:    string(enums.SyncStatusDone),
			Ver:           int64(maxVer + 1),
		})
		if err != nil {
			return
		}

		// 设置grpc商品属性键同步远程对象
		_, err = q.UpsertSyncRemote(ctx, sqlc.UpsertSyncRemoteParams{
			SyncID:    syncOfProductSpec.ID,
			RemoteObj: remoteObjOfProductSpec,
			RemotePk:  remotePkOfProductSpec,
			Data:      bilinProdSkuPtr.Raw,
		})
		if err != nil {
			return
		}
	}
	slog.Infof("12 批量upsert商品规格、设置商品规格同步映射、设置商品规格同步日志、设置grpc商品规格同步远程对象 ok")

	slog.Infof("b 更新商品 ok")
	return
}

// addInventoryWarehouse 添加商品中心商品
func (s *ProductServiceImp) createBilinProd(ctx context.Context, ptr *CreateProductParam, prodTypeID uint32) (bilinProd model.BilinProd, bilinSkuPtrMap model.BilinProdSkuPtrMap, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	brandID := ptr.BrandID

	webToken, err := s.genProdToken(ctx, brandID)
	if err != nil {
		return
	}

	var skus []*bilinprod.CreateSkuView

	for _, spec := range ptr.Specs {
		var skuItemViews []*bilinprod.SkuItemView
		for _, attr := range spec.Attributes {
			skuItemViews = append(skuItemViews, &bilinprod.SkuItemView{
				Name: attr.Key,
				Item: attr.Value,
			})
		}

		skus = append(skus, &bilinprod.CreateSkuView{
			Price:  uint32(spec.Price),
			Weight: 0,
			Items:  skuItemViews,
		})
	}

	req := &bilinprod.CreateProdRequest{
		Token:             webToken,
		PStoreId:          0,
		Nature:            bilinprod.Nature_NATURE_SALE,
		ProdTypeId:        prodTypeID,
		Name:              ptr.Name,
		Phonetic:          "BG",
		Unit:              "份",
		TitlePics:         util.LeftToString(ptr.Pics, 4),
		DetailPics:        util.LeftToString(ptr.IntroPics, 20),
		DetailDescription: ptr.Intro,
		SkuList:           skus,
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	reply, err := s.proxy.BilinProdCl.CreateProd(ctxWithTimout, req)
	if err != nil {
		errReply := s.proxy.GetBilinProdErrorInfo(err)
		if errReply != nil {
			err = fmt.Errorf("商品中心-创建商品错误:%s", errReply.ErrorMessage)
			slog.Infof("BilinProdCl.CreateProd err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
		}
		return
		}

	bilinProd.RemotePkOfProd = int64(reply.Id)
	bytesOfProd, err := json.Marshal(reply)
	if err != nil {
		return
	}
	bilinProd.Raw = json.RawMessage(bytesOfProd)

	bilinSkuPtrMap = make(model.BilinProdSkuPtrMap)
	skuViews := reply.Skus
	var bytesOfProdSku []byte
	for _, skuView := range skuViews {
		k := util.JoinString(skuView.Items, "/")
		bytesOfProdSku, err = json.Marshal(reply)
		if err != nil {
			return
		}
		bilinSkuPtrMap[k] = &model.BilinProdSku{
			RemotePkOfSku: int64(skuView.Id),
			Raw:           json.RawMessage(bytesOfProdSku),
		}
	}
	return
}

// editBilinProd 编辑商品中心商品
func (s *ProductServiceImp) editBilinProd(ctx context.Context, ptr *UpdateProductParam, remotePkOfProd uint64, prodTypeID uint32) (bilinProd model.BilinProd, bilinSkuPtrMap model.BilinProdSkuPtrMap, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	brandID := ptr.BrandID

	webToken, err := s.genProdToken(ctx, brandID)
	if err != nil {
		return
	}

	var skus []*bilinprod.CreateSkuView

	for _, spec := range ptr.Specs {
		var skuItemViews []*bilinprod.SkuItemView
		for _, attr := range spec.Attributes {
			skuItemViews = append(skuItemViews, &bilinprod.SkuItemView{
				Name: attr.Key,
				Item: attr.Value,
			})
		}

		skus = append(skus, &bilinprod.CreateSkuView{
			Price:  uint32(spec.Price),
			Weight: 0,
			Items:  skuItemViews,
		})
	}

	req := &bilinprod.EditProdRequest{
		Token:             webToken,
		PStoreId:          0,
		Id:                remotePkOfProd,
		ProdTypeId:        prodTypeID,
		Name:              ptr.Name,
		Phonetic:          "BG",
		Unit:              "份",
		TitlePics:         util.LeftToString(ptr.Pics, 4),
		DetailPics:        util.LeftToString(ptr.IntroPics, 20),
		DetailDescription: ptr.Intro,
		SkuList:           skus,
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	reply, err := s.proxy.BilinProdCl.EditProd(ctxWithTimout, req)
	if err != nil {
		errReply := s.proxy.GetBilinProdErrorInfo(err)
		if errReply != nil {
			err = fmt.Errorf("商品中心-编辑商品错误:%s", errReply.ErrorMessage)
			slog.Infof("BilinProdCl.EditProd err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
		}
		return
	}

	bilinProd.RemotePkOfProd = int64(reply.Id)
	bytesOfProd, err := json.Marshal(reply)
	if err != nil {
		return
	}
	bilinProd.Raw = json.RawMessage(bytesOfProd)

	bilinSkuPtrMap = make(model.BilinProdSkuPtrMap)
	skuViews := reply.Skus
	var bytesOfProdSku []byte
	for _, skuView := range skuViews {
		k := util.JoinString(skuView.Items, "/")
		bytesOfProdSku, err = json.Marshal(reply)
		if err != nil {
			return
		}
		bilinSkuPtrMap[k] = &model.BilinProdSku{
			RemotePkOfSku: int64(skuView.Id),
			Raw:           json.RawMessage(bytesOfProdSku),
		}
	}
	return
}

func (s *ProductServiceImp) getBilinProd(ctx context.Context, brandID int64, remotePkOfProduct int64) (bilinProd model.BilinProd, bilinSkuPtrMap model.BilinProdSkuPtrMap, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	webToken, err := s.genProdToken(ctx, brandID)
	if err != nil {
		return
	}
	req := &bilinprod.GetProdInfoRequest{
		Token:    webToken,
		PStoreId: 0,
		Id:       uint64(remotePkOfProduct),
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()

	reply, err := s.proxy.BilinProdCl.GetProdInfo(ctxWithTimout, req)
	if err != nil {
		errReply := s.proxy.GetBilinProdErrorInfo(err)
		if errReply != nil {
			err = fmt.Errorf("商品中心-获取商品错误:%s", errReply.ErrorMessage)
			slog.Infof("BilinProdCl.CreateProd err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
		}
		return
	}
	bilinProd.RemotePkOfProd = int64(reply.Id)
	bytesOfProd, err := json.Marshal(reply)
	if err != nil {
		return
	}
	bilinProd.Raw = json.RawMessage(bytesOfProd)

	bilinSkuPtrMap = make(model.BilinProdSkuPtrMap)
	skuViews := reply.Skus
	var bytesOfProdSku []byte
	for _, skuView := range skuViews {
		k := util.JoinString(skuView.Items, "/")
		bytesOfProdSku, err = json.Marshal(reply)
		if err != nil {
			return
		}
		bilinSkuPtrMap[k] = &model.BilinProdSku{
			RemotePkOfSku: int64(skuView.Id),
			Raw:           json.RawMessage(bytesOfProdSku),
		}
	}
	return
}

type BilinProdTypeNameToID map[string]uint32

// addInventoryWarehouse 确保商品中心商品分类可用(一级分类+分类)
func (s *ProductServiceImp) ensureBilinProdType(ctx context.Context, brandID int64) (bilinProdTypeOfSub uint32, err error) {
	slog := util.NewContextLogger(ctx, s.slog)

	webToken, err := s.genProdToken(ctx, brandID)
	if err != nil {
		return
	}
	req := &bilinprod.GetProdTypeListRequest{
		Token:    webToken,
		PStoreId: 0,
		Nature:   bilinprod.Nature_NATURE_SALE,
	}

	ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
	defer cancel()
	slog.Infof("GetProdTypeList req =>%v", req)
	reply, err := s.proxy.BilinProdCl.GetProdTypeList(ctxWithTimout, req)
	if err != nil {
		errReply := s.proxy.GetBilinProdErrorInfo(err)
		if errReply != nil {
			err = fmt.Errorf("商品中心-查询比邻品牌下商品分类列表错误:%s", errReply.ErrorMessage)
			slog.Infof("BilinProdCl.GetProdTypeList err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
		}
		return
	}
	slog.Infof("prodTypeNames=>", reply.ProdTypes)

	cBrand := s.cacher.FetchBrand(brandID)
	if cBrand == nil {
		err = fmt.Errorf("not found brand of brandID:%d", brandID)
		return
	}
	parentName := fmt.Sprintf("(星团专用)%s", cBrand.Name)
	sonName := fmt.Sprintf("未分类(星团专用)%s", cBrand.Name)

	bilinProdTypeNameToIDMap := make(BilinProdTypeNameToID)
	prodTypeNames := util.FoldLeftStringSlice(util.ToGenericSlice(reply.ProdTypes), func(aggr []string, v interface{}) []string {
		val := v.(*bilinprod.ProdTypeView)
		aggr = append(aggr, val.Name)
		bilinProdTypeNameToIDMap[val.Name] = val.Id
		if len(val.Subs) > 0 {
			subProdTypeNames := util.MapToString(util.ToGenericSlice(val.Subs), func(vSub interface{}) string {
				valSub := vSub.(*bilinprod.ProdTypeView)
				bilinProdTypeNameToIDMap[valSub.Name] = valSub.Id
				return valSub.Name
			})
			aggr = append(aggr, subProdTypeNames...)
		}
		return aggr
	}, []string{})
	slog.Infof("prodTypeNames=>", prodTypeNames)

	isParentOk := util.StringContains(prodTypeNames, parentName)
	isSonOk := util.StringContains(prodTypeNames, sonName)

	var parentID uint32
	if !isParentOk {
		slog.Infof("无法找到一级分类,创建...")
		req := &bilinprod.CreateProdTypeRequest{
			Token:    webToken,
			PStoreId: 0,
			Name:     parentName,
			Nature:   bilinprod.Nature_NATURE_SALE,
		}

		ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
		defer cancel()

		reply, err2 := s.proxy.BilinProdCl.CreateProdType(ctxWithTimout, req)
		if err2 != nil {
			errReply := s.proxy.GetBilinProdErrorInfo(err2)
			if errReply != nil {
				err = fmt.Errorf("商品中心-创建比邻品牌的一级商品分类错误:%s", errReply.ErrorMessage)
				slog.Infof("BilinProdCl.CreateProdType err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
			}
			return
		}
		parentID = reply.Id
	} else {
		parentID = bilinProdTypeNameToIDMap[parentName]
	}

	if !isSonOk {
		slog.Infof("无法找到二级分类,创建...")
		req := &bilinprod.CreateProdTypeRequest{
			Token:    webToken,
			PStoreId: 0,
			Name:     sonName,
			ParentId: parentID,
			Nature:   bilinprod.Nature_NATURE_SALE,
		}
		ctxWithTimout, cancel := s.proxy.NewContextWith(ctx)
		defer cancel()

		reply, err2 := s.proxy.BilinProdCl.CreateProdType(ctxWithTimout, req)
		if err2 != nil {
			errReply := s.proxy.GetBilinProdErrorInfo(err2)
			if errReply != nil {
				err = fmt.Errorf("商品中心-创建比邻品牌二级商品分类错误:%s", errReply.ErrorMessage)
				slog.Infof("BilinProdCl.CreateProdType err====>type:%s,code:%s,msg:%s", errReply.ErrorType, errReply.ErrorCode, errReply.ErrorMessage)
			}
			return
		}
		bilinProdTypeOfSub = reply.Id
	} else {
		bilinProdTypeOfSub = bilinProdTypeNameToIDMap[sonName]
	}

	return
}
